package com.example.mq.mqserver.core;

import com.example.mq.common.MqException;

// 使用这个类, 来实现交换机的转发规则
// 同时, 也借助这个类验证 bindingKey 是否合法
public class Router {
    // bindingKey 的构造规则
    // 1. 数字, 字母, 下划线
    // 2. 使用 . 进行分割
    // 3.允许存在 * 和 # 通配符
    public boolean checkBindingKey(String bindingKey) {
        if(bindingKey.length() == 0) {
            // 空字符串 - 合法. 使用 direct / fanout 交换机时, bindingKey 允许为 ""
            return true;
        }
        // 检查非法字符
        for(int i=0;i<bindingKey.length();i++) {
            char ch = bindingKey.charAt(i);
            if((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
                continue;
            }
            if(ch >= '0' && ch <= '9') {
                continue;
            }
            if(ch == '_' || ch == '.' || ch == '*' || ch == '#') {
                continue;
            }
            // 合法情况都已检验, 则剩下的就是非法情况
            return false;
        }

        // 检查 # 和 * 是否是独立部分
        // aaa.*.bbb - 合法, aaa.a*.bbb - 非法
        String[] words = bindingKey.split("\\.");
        for(String word : words) {
            if(word.length() > 1 && (word.contains("*") || word.contains("#"))) {
                return false;
            }
        }

        // 此处约定, 通配符之间的相邻关系 (有些功能不好实现, 且意义不大 [rabbitMQ是支持的, 我这里嫌麻烦, 因此不支持])
        // 1. aaa.#.#.bbb   => 非法
        // 2. aaa.#.*.bbb   => 非法
        // 3. aaa.*.#.bbb   => 非法
        // 4. aaa.*.*.bbb   => 合法
        for(int i=0;i<words.length-1;i++) {
            // aaa.#.#.bbb
            if(words[i].equals("#") && words[i+1].equals("#")) {
                return false;
            }
            // aaa.#.*.bbb
            if(words[i].equals("#") && words[i+1].equals("*")) {
                return false;
            }
            // aaa.*.$.bbb
            if(words[i].equals("*") && words[i+1].equals("#")) {
                return false;
            }
        }

        // 所有非法都剔出完毕, 剩下的就是合法
        return true;
    }

    // routingKey 的构造规则:
    // 1. 数字, 字母, 下划线
    // 2. 使用 . 进行分割
    public boolean checkRoutingKey(String routingKey) {
        if(routingKey.length() == 0) {
            // 空字符串 - 合法. 使用 fanout 交换机时, routingKey 就可以设置为 ""
            return true;
        }
        for(int i=0;i<routingKey.length();i++) {
            char ch = routingKey.charAt(i);
            if((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
                continue;
            }
            if(ch >= '0' && ch <= '9') {
                continue;
            }
            if(ch == '_' || ch == '.') {
                continue;
            }
            // 合法情况都已检验, 则剩下的就是非法情况
            return false;
        }
        return true;
    }

    // 该方法用来判定 消息 是否可以转发到这个 绑定 对应的 队列
    public boolean route(ExchangeType exchangeType, Binding binding, Message message) throws MqException {
        if(exchangeType == ExchangeType.FANOUT) {
            // exchange 为 fanout : 直接转发
            return true;
        }else if(exchangeType == ExchangeType.TOPIC) {
            // exchange 为 topic : binding 中的 bindingKey 和 message 中 basicProperties 中的 routingKey, 能匹配的话, 则可以转发到这个绑定对应的队列
            return routeTopic(binding, message);
        }else {
            throw new MqException("[Router - route] 交换机类型非法! exchangeType=" + exchangeType);
        }
    }

    // Topic 交换机中 bindingKey 和 routingKey 的匹配规则 - dp实现
    private boolean sub(Binding binding, Message message) {
        // a 中带通配符
        String[] a = binding.getBindingKey().split("\\.");
        String[] b = message.getRoutingKey().split("\\.");

        int lena = a.length;
        int lenb = b.length;
        // * 匹配一个, # 匹配任意个
        boolean[][] dp = new boolean[lena+5][lenb+5];
        for(int i=0;i<lena;i++) {
            dp[i][0]=true;
        }
        for(int i=0;i<lena;i++) {
            for(int j=0;j<lenb;j++) {
                if(a[i].equals("*") || a[i]==b[j]) {
                    dp[i][j]=dp[i-1][j-1];
                }else if(a[i].equals("#")) {
                    dp[i][j]=true;
                }else {
                    dp[i][j]=false;
                }
            }
        }
        return dp[lena-1][lenb-1];
    }

    // Topic 交换机中 bindingKey 和 routingKey 的匹配规则 - dp实现
    private boolean routeTopic(Binding binding, Message message) {
        // bindingKey 是带通配符, 用来匹配的
        // routingKey 是固定的

        // 先把这两个 key 进行切分
        String[] bindingTokens = binding.getBindingKey().split("\\.");
        String[] routingTokens = message.getRoutingKey().split("\\.");

        // 引入两个下标, 指向上述两个数组, 初始情况下 为0
        int bindingIndex = 0;
        int routingIndex = 0;

        while(bindingIndex < bindingTokens.length && routingIndex < routingTokens.length) {
            if(bindingTokens[bindingIndex].equals("*")) {
                // * 可以匹配到任意一个部分
                bindingIndex++;
                routingIndex++;
                continue;
            }else if(bindingTokens[bindingIndex].equals("#")) {
                // # 匹配任意个字符
                bindingIndex++;

                // 如果 # 后面没有东西了, 则一定可以匹配成功
                if(bindingIndex == bindingTokens.length) {
                    return true;
                }

                // # 后面还有东西, 则用该内容, 去匹配 routingKey 的后续内容, 如果能匹配到, 则说明 * 匹配成功, 匹配不到, 说明 * 匹配失败
                // findNextMatch 用来查找该部分在 routingKey 中的位置. 找到了返回该下标, 没找到返回 -1.
                routingIndex = findNextMatch(routingTokens, routingIndex, bindingTokens[bindingIndex]);
                // * 匹配失败
                if(routingIndex == -1) {
                    return false;
                }
                // * 匹配成功
                bindingIndex++;
                routingIndex++;
            } else {
                // 如果遇到普通字符串, 要求两边的内容是一样的
                if(!bindingTokens[bindingIndex].equals(routingTokens[routingIndex])) {
                    return false;
                }
                bindingIndex++;
                routingIndex++;
            }
        }
        // while 循环结束, 则需要判定是否达到末尾
        if(bindingIndex == bindingTokens.length && routingIndex == routingTokens.length) {
            return true;
        }

        return false;
    }

    /**
     * 匹配 * 的规则
     * @param routingTokens
     * @param routingIndex
     * @param bindingToken
     * @return
     */
    private int findNextMatch(String[] routingTokens, int routingIndex, String bindingToken) {
        for(int i=routingIndex;i<routingTokens.length;i++) {
            if(routingTokens[i].equals(bindingToken)) {
                return i;
            }
        }
        return -1;
    }
}
